class Player:
    def __init__(self, isFirst: bool, array: list) -> None:
        self.belong = isFirst
        self.array = array
        self.height = 4
        self.width = 8
        self.half_width = 4
        self.empty_score = 12  # 在代价函数中每个空位对应的得分，12分等价于一个数字为8的格子的得分

    def output(self, currentRound, board, mode):
        """暴力枚举单次的所有可能下棋/移动方式，选出代价估计价值最高的方式返回"""

        if mode == 'position':  # 给出己方下棋的位置
            another = board.getNext(self.belong, currentRound)  # 己方的允许落子点
            available = board.getNone(not self.belong)  # 对方的允许落子点

            bestchoice = None  # 初始化当前选择
            bestvalue = float('-inf')  # 初始化当前代价（负无穷）

            if another != ():  # 本方位置落子
                bestchoice = another
                bestvalue = self.cost_function(board, mode, another, self.belong)

            for pos in available:  # 对方位置落子
                currentvalue = self.cost_function(board, mode, pos, not self.belong)
                if bestvalue < currentvalue:
                    bestvalue = currentvalue
                    bestchoice = pos
            return bestchoice

        elif mode == 'direction':  # 给出己方合并的方向
            directionList = [0, 1, 2, 3]

            bestchoice = None  # 初始化
            bestvalue = float('-inf')

            for direction in directionList:
                imitation = board.copy()
                if imitation.move(self.belong, direction):  # board类的move函数自带调整棋盘，并返回是否变化
                    currentvalue = self.cost_function(imitation, mode)
                    if bestvalue < currentvalue:
                        bestvalue = currentvalue
                        bestchoice = direction
            return bestchoice

        else:
            return

    def cost_function(self, chessboard, mode, position=None, belong=True):
        """如果模式是position，调用时就需要给出落子的位置和归属，
        True表示下在先手方，False表示下在后手方，模式是direction
        就不需要给出position和belong参数"""
        if mode == "position":
            return self.cost_function_1(chessboard, position, belong)
        else:
            return self.cost_function_2(chessboard)

    def cost_function_1(self, chessboard, position: tuple, belong: bool):  # 对应mode为放棋子的情况,第一个代价函数
        score = 0
        if self.belong == belong:  # 表明棋子下在自己这边
            score += 2  # 下在自己这边得2分
            up = self.get_neighbor_2(chessboard, position, 0)  # 获取该位置上方的棋子值
            down = self.get_neighbor_2(chessboard, position, 1)  # 获取该位置下方的棋子值
            left = self.get_neighbor_2(chessboard, position, 2)  # 获取该位置左方的棋子值
            right = self.get_neighbor_2(chessboard, position, 3)  # 获取该位置右方的棋子值
            score += self.put_chessman_score(up, down, belong)
            score += self.put_chessman_score(left, right, belong)
            return score
        else:  # 表明棋子下在对方那,此时棋子属于对方
            up = self.get_neighbor_3(chessboard, position, 0)  # 获取该位置上方的棋子值
            down = self.get_neighbor_3(chessboard, position, 1)  # 获取该位置下方的棋子值
            left = self.get_neighbor_3(chessboard, position, 2)  # 获取该位置左方的棋子值
            right = self.get_neighbor_3(chessboard, position, 3)  # 获取该位置右方的棋子值
            score += self.put_chessman_score(up, down, belong)
            score += self.put_chessman_score(left, right, belong)
            return score

    def cost_function_2(self, chessboard):  # 对应的mode是移动的情况
        score = 0
        for position in self.return_pos(self.belong):  # 对己方的16个格子进行遍历
            if chessboard.getBelong(position) != self.belong:  # 说明己方位置被对方占了
                score += self.be_occupied_score(chessboard.getValue(position))
                """相比于其它情况，己方位置被占减少的分数比对应更大，
                这样就可以鼓励AI在能够将该位置的棋子吃掉尽可能选择吃掉"""
            else:  # 对应的该位置是属于自己的
                if position not in chessboard.board:
                    score += self.empty_score  # 空位也是非常宝贵的！
                else:  # 此位置有自己的棋子，score加上棋子对应的分值,还有所处棋局形势对应的分值
                    for direction in range(4):
                        score += self.situation_score(chessboard, position, direction)
                    score += self.my_chessman_score(chessboard.getValue(position))

        for position in self.return_pos(not self.belong):  # 对对方的16个格子进行遍历
            if chessboard.getBelong(position) == self.belong:  # 说明对方位置被己方占了
                score += self.occupy_score(chessboard.getValue(position))
                score += self.my_chessman_score(chessboard.getValue(position))
                for direction in range(4):
                    score += self.situation_score(chessboard, position, direction)
            else:
                if position in chessboard.board:
                    score += self.rival_chessman_score(chessboard.getValue(position))
                    # 对应的情况是这个位置属于对方且上面有棋
        return score

    def situation_score(self, chessboard, position: tuple, direction: int) -> int:
        """该函数用于判断己方某棋子目前所处的状态(因此传入的position要求对应己方棋子)，
        主要依据是看它是否存在被对方吃的可能性，或者能安全吃掉对方的可能性，
        但是没有考虑存在陷阱的情况(见下面的讨论),返回值是该状态对应的得分
        用于在mode为direction时调用"""
        is_adjacent = True  # 该参数用于判断position是否安全
        next_position = self.update_position(position, direction)
        if not self.is_in_chess(next_position):  # 表明不能在该方向上继续移动,返回0分
            return 0
        while self.is_in_chess(next_position) and next_position not in chessboard.board \
                and self.belong == chessboard.getBelong(next_position):
            # 表明该格子为空且属于自己,继续向前走
            is_adjacent = False  # 表明该棋子在该方向上没有直接相邻敌对格，因此不存在被吃风险,是安全的
            next_position = self.update_position(next_position, direction)

        if not self.is_in_chess(next_position):
            return 0  # 此时该棋子不位于边界，且该方向上没有棋子,返回0分
        elif self.belong != chessboard.getBelong(next_position):
            # 说明该格子属于对方
            if next_position in chessboard.board:  # 这表明不仅格子属于对方，而且上面还有棋
                if is_adjacent:  # 表明初始位置和最终的next_position相邻
                    if chessboard.getValue(next_position) == chessboard.getValue(position):
                        return -self.threaten_score(chessboard.getValue(position))  # 存在被对方吃的可能性，返回对应的负分
                        # 此时未考虑故意吸引的可能性：比如己方在另一个方向恰好有2倍的值，在对方吃掉这颗棋后可以马上把对方的棋吃掉
                    else:
                        return 0  # 此时相邻的对方棋与自己值不相等，不构成威胁
                else:
                    # 说明此时初始位置与最终的next_position不相邻，因此我方可以吃它，但它不能吃我方
                    if chessboard.getValue(next_position) == chessboard.getValue(position):
                        return self.threaten_score(chessboard.getValue(position))  # 可以吃对面，局势有利
                        # 此时也未考虑对方故意吸引的可能性：理由同上
                    else:
                        return 0  # 此时对方的棋与己方的棋值不相等，
            else:  # 此时该格子属于对方，但是上面没有棋
                if is_adjacent:  # 表明初始位置有被对方吃的可能性
                    while self.is_in_chess(next_position) and next_position not in chessboard.board:
                        # 继续向指定方向走直到碰到有棋子为止
                        next_position = self.update_position(next_position, direction)
                    if not self.is_in_chess(next_position):
                        return 0  # 表明从初始位置沿该方向上无棋子
                    elif self.belong == chessboard.getBelong(next_position):
                        # 考虑到存在有己方棋子跑到人家人家棋盘的可能性
                        return 0  # 此时初始位置的棋子不存在被吃的可能性
                    else:
                        # 对应的情况是有对方棋子
                        if chessboard.getValue(next_position) == chessboard.getValue(position):
                            return -self.threaten_score(chessboard.getValue(position))  # 此时有被对方吃的可能性,返回负分
                        else:
                            return 0  # 此时无威胁
                else:
                    # 此时对方吃不到自己，没有威胁
                    return 0
        else:
            # 该情况意味着从初始位置出发沿指定方向移动碰到的第一个棋子是自己人
            return 0  # 这里没有考虑两个相同的己方值在一块的情况加分，我觉得直接统计格子中的总分值更好?

    def get_neighbor_2(self, chessboard, position: tuple, direction: int):
        """该函数在下棋模式调用，且对应下在己方位置的情况，
        用于获取该棋子在该方向上相邻的己方棋子"""
        next_position = self.update_position(position, direction)

        while self.is_in_chess(next_position) and next_position not in chessboard.board \
                and self.belong == chessboard.getBelong(next_position):
            next_position = self.update_position(next_position, direction)
        if self.is_in_chess(next_position) and self.belong == chessboard.getBelong(next_position):
            return chessboard.getValue(next_position)
        else:
            return False  # 对应该方向上没有己方棋子

    def get_neighbor_3(self, chessboard, position: tuple, direction: int):
        """该函数在下棋模式调用，且对应下在对方位置的情况
        用于获取该棋子在该方向上相邻的对方棋子(此时该棋子是属于对方的)"""
        next_position = self.update_position(position, direction)
        while self.is_in_chess(next_position) and next_position not in chessboard.board \
                and self.belong != chessboard.getBelong(next_position):
            next_position = self.update_position(next_position, direction)
        if self.is_in_chess(next_position) and self.belong != chessboard.getBelong(next_position):
            return chessboard.getValue(next_position)
        else:
            return False  # 对应该方向上没有对方的棋子

    def return_pos(self, belong):
        if belong:  # 返回先手方的遍历格子
            for i in range(self.height):
                for j in range(self.half_width):
                    yield i, j
        else:  # 返回后手方的遍历格子
            for i in range(self.height):
                for j in range(self.half_width, self.width):
                    yield i, j

    def is_in_chess(self, position):  # 用于判断该位置是否在棋盘中
        return (0 <= position[0] < self.width) and (0 <= position[1] < self.height)

    @staticmethod
    def my_chessman_score(value):  # 返回己方每个值对应多少得分,封装在函数中便于修改,在移动模式的代价函数中调用
        return 3 * int(pow(2, value))
        # 没有直接返回2的value次方是为了鼓励AI尽可能合并大的

    @staticmethod
    def rival_chessman_score(value: int):  # 返回对方每个值对应多少得分,封装在函数中便于修改，在移动模式的代价函数中调用
        return -int(pow(2, value))

    @staticmethod
    def be_occupied_score(value: int):
        """在己方棋盘被对方占据的情况返回的分数,返回的是一个负分，鼓励AI吃掉对方在自己位置的棋子
        封装在函数中便于修改，在移动模式的代价函数中调用"""
        return -int((pow(2, value) + pow(2, value)))

    @staticmethod
    def occupy_score(value: int):
        """在对方棋盘被己方占据的情况返回的分数，返回一个负分，鼓励AI将该棋子移动会自己的地盘
        封装在函数中便于修改，在移动模式的代价函数中调用"""
        return -int(pow(2, value))

    @staticmethod
    def threaten_score(value: int):
        """对应的是棋子存在吃对方和被对方吃的可能性时返回的分数,
        封在函数中便于修改，在get_neighbor_1函数中调用"""
        return int(pow(2, value))

    def put_chessman_score(self, value_1, value_2, belong):
        """value_1,value_2对应落子位置的某一方向上的两个相邻的棋子
        belong是用来确定是下在己方位置还是敌方位置的,具体返回多少分数值
        返回的模式也是可以修改的"""
        score = 0
        if self.belong == belong:  # 对应下在自己这边的情况
            if value_1 and value_2:
                if value_1 == 5 and value_2 == 5:  # 下棋位置卡在了两个32之间，不好
                    score -= 32
                elif value_1 >= 6 and value_2 >= 6:  # 下棋位置卡在了两个大佬之间，不好
                    score -= 64
                elif value_1 <= 4 and value_2 <= 4:  # 下棋位置附近都是小值，便于合并
                    score += 16
            return score
        else:  # 对应下在对方位置的情况
            if value_1 and value_2:
                if value_1 == 5 and value_2 == 5:  # 下棋位置卡在了两个32之间，阻碍对方
                    score += 32
                elif value_1 >= 6 and value_2 >= 6:  # 下棋位置卡在了两个大佬之间，阻碍对方
                    score += 64
                elif value_1 <= 4 and value_2 <= 4:  # 下棋位置附近都是小值，给对方送钱了
                    score -= 16
            else:
                score -= 8  # 下棋位置附近没有对方棋子，给对方送钱了
            return score

    @staticmethod
    def update_position(position, direction):  # 在目前位置向给定方向移动一格
        if direction == 0:  # 对应向上移动
            return position[0], position[1] - 1
        elif direction == 1:  # 对应向下移动
            return position[0], position[1] + 1
        elif direction == 2:  # 对应向左移动
            return position[0] - 1, position[1]
        elif direction == 3:  # 对应向右移动
            return position[0] + 1, position[1]



